package com.jse.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 汉字转拼音类
 *
 * @author stuxuhai (dczxxuhai@gmail.com)
 */
public final class Pinyin {
	public enum PinyinFormat {
	    WITH_TONE_MARK, WITHOUT_TONE, WITH_TONE_NUMBER;
	}
	private static List<String> dict = new ArrayList<String>();
	private static final Map<String, String> PINYIN_TABLE = Streams.resource("/META-INF/pinyin/pinyin.dict");
	private static final Map<String, String> MUTIL_PINYIN_TABLE = Streams.resource("/META-INF/pinyin/mutil_pinyin.dict");
	private static final DoubleArrayTrie DOUBLE_ARRAY_TRIE = new DoubleArrayTrie();
	private static final String PINYIN_SEPARATOR = ","; // 拼音分隔符
	private static final char CHINESE_LING = '〇';
	private static final String ALL_UNMARKED_VOWEL = "aeiouv";
	private static final String ALL_MARKED_VOWEL = "āáǎàēéěèīíǐìōóǒòūúǔùǖǘǚǜ"; // 所有带声调的拼音字母

	static {
		for (String word : MUTIL_PINYIN_TABLE.keySet()) {
			dict.add(word);
		}
		Collections.sort(dict);
		DOUBLE_ARRAY_TRIE.build(dict);
	}

	private Pinyin() {
	}

	/**
	 * 将带声调格式的拼音转换为数字代表声调格式的拼音
	 * 
	 * @param pinyinArrayString
	 *            带声调格式的拼音
	 * @return 数字代表声调格式的拼音
	 */
	private static String[] convertWithToneNumber(String pinyinArrayString) {
		String[] pinyinArray = pinyinArrayString.split(PINYIN_SEPARATOR);
		for (int i = pinyinArray.length - 1; i >= 0; i--) {
			boolean hasMarkedChar = false;
			String originalPinyin = pinyinArray[i].replace("ü", "v"); // 将拼音中的ü替换为v

			for (int j = originalPinyin.length() - 1; j >= 0; j--) {
				char originalChar = originalPinyin.charAt(j);

				// 搜索带声调的拼音字母，如果存在则替换为对应不带声调的英文字母
				if (originalChar < 'a' || originalChar > 'z') {
					int indexInAllMarked = ALL_MARKED_VOWEL.indexOf(originalChar);
					int toneNumber = indexInAllMarked % 4 + 1; // 声调数
					char replaceChar = ALL_UNMARKED_VOWEL.charAt((indexInAllMarked - indexInAllMarked % 4) / 4);
					pinyinArray[i] = originalPinyin.replace(String.valueOf(originalChar), String.valueOf(replaceChar))
							+ toneNumber;
					hasMarkedChar = true;
					break;
				}
			}
			if (!hasMarkedChar) {
				// 找不到带声调的拼音字母说明是轻声，用数字5表示
				pinyinArray[i] = originalPinyin + "5";
			}
		}

		return pinyinArray;
	}

	/**
	 * 将带声调格式的拼音转换为不带声调格式的拼音
	 * 
	 * @param pinyinArrayString
	 *            带声调格式的拼音
	 * @return 不带声调的拼音
	 */
	private static String[] convertWithoutTone(String pinyinArrayString) {
		String[] pinyinArray;
		for (int i = ALL_MARKED_VOWEL.length() - 1; i >= 0; i--) {
			char originalChar = ALL_MARKED_VOWEL.charAt(i);
			char replaceChar = ALL_UNMARKED_VOWEL.charAt((i - i % 4) / 4);
			pinyinArrayString = pinyinArrayString.replace(String.valueOf(originalChar), String.valueOf(replaceChar));
		}
		// 将拼音中的ü替换为v
		pinyinArray = pinyinArrayString.replace("ü", "v").split(PINYIN_SEPARATOR);
		return pinyinArray;
	}

	/**
	 * 将带声调的拼音格式化为相应格式的拼音
	 * 
	 * @param pinyinString
	 *            带声调的拼音
	 * @param pinyinFormat
	 *            拼音格式：WITH_TONE_NUMBER--数字代表声调，WITHOUT_TONE--不带声调，WITH_TONE_MARK--带声调
	 * @return 格式转换后的拼音
	 */
	private static String[] formatPinyin(String pinyinString, PinyinFormat pinyinFormat) {
		if (pinyinFormat == PinyinFormat.WITH_TONE_MARK) {
			return pinyinString.split(PINYIN_SEPARATOR);
		} else if (pinyinFormat == PinyinFormat.WITH_TONE_NUMBER) {
			return convertWithToneNumber(pinyinString);
		} else if (pinyinFormat == PinyinFormat.WITHOUT_TONE) {
			return convertWithoutTone(pinyinString);
		}
		return new String[0];
	}

	/**
	 * 将单个汉字转换为相应格式的拼音
	 * 
	 * @param c
	 *            需要转换成拼音的汉字
	 * @param pinyinFormat
	 *            拼音格式：WITH_TONE_NUMBER--数字代表声调，WITHOUT_TONE--不带声调，WITH_TONE_MARK--带声调
	 * @return 汉字的拼音
	 */
	public static String[] convertToPinyinArray(char c, PinyinFormat pinyinFormat) {
		String pinyin = PINYIN_TABLE.get(String.valueOf(c));
		if ((pinyin != null) && (!"null".equals(pinyin))) {
			Set<String> set = new LinkedHashSet<String>();
			for (String str : formatPinyin(pinyin, pinyinFormat)) {
				set.add(str);
			}
			return set.toArray(new String[set.size()]);
		}
		return new String[0];
	}

	/**
	 * 将单个汉字转换成带声调格式的拼音
	 * 
	 * @param c
	 *            需要转换成拼音的汉字
	 * @return 字符串的拼音
	 */
	public static String[] convertToPinyinArray(char c) {
		return convertToPinyinArray(c, PinyinFormat.WITH_TONE_MARK);
	}

	/**
	 * 将字符串转换成相应格式的拼音
	 * 
	 * @param str
	 *            需要转换的字符串
	 * @param separator
	 *            拼音分隔符
	 * @param pinyinFormat
	 *            拼音格式：WITH_TONE_NUMBER--数字代表声调，WITHOUT_TONE--不带声调，WITH_TONE_MARK--带声调
	 * @return 字符串的拼音
	 * @throws PinyinException
	 */
	public static String convertToPinyinString(String str, String separator, PinyinFormat pinyinFormat)
			throws RuntimeException {
		str = Chinese.convertToSimplifiedChinese(str);
		StringBuilder sb = new StringBuilder();
		int i = 0;
		int strLen = str.length();
		while (i < strLen) {
			String substr = str.substring(i);
			List<Integer> commonPrefixList = DOUBLE_ARRAY_TRIE.commonPrefixSearch(substr);
			if (commonPrefixList.size() == 0) {
				char c = str.charAt(i);
				// 判断是否为汉字或者〇
				if (Chinese.isChinese(c) || c == CHINESE_LING) {
					String[] pinyinArray = convertToPinyinArray(c, pinyinFormat);
					if (pinyinArray != null) {
						if (pinyinArray.length > 0) {
							sb.append(pinyinArray[0]);
						} else {
							throw new RuntimeException("Can't convert to pinyin: " + c);
						}
					} else {
						sb.append(str.charAt(i));
					}
				} else {
					sb.append(c);
				}
				i++;
			} else {
				String words = dict.get(commonPrefixList.get(commonPrefixList.size() - 1));
				String[] pinyinArray = formatPinyin(MUTIL_PINYIN_TABLE.get(words), pinyinFormat);
				for (int j = 0, l = pinyinArray.length; j < l; j++) {
					sb.append(pinyinArray[j]);
					if (j < l - 1) {
						sb.append(separator);
					}
				}
				i += words.length();
			}

			if (i < strLen) {
				sb.append(separator);
			}
		}
		return sb.toString();
	}

	/**
	 * 将字符串转换成带声调格式的拼音
	 * 
	 * @param str
	 *            需要转换的字符串
	 * @param separator
	 *            拼音分隔符
	 * @return 转换后带声调的拼音
	 * @throws PinyinException
	 */
	public static String to(String str, String separator) throws RuntimeException {
		return convertToPinyinString(str, separator, PinyinFormat.WITH_TONE_MARK);
	}
	public static String to(String str, String separator,String is) throws RuntimeException {
		//WITH_TONE_NUMBER--数字代表声调，WITHOUT_TONE--不带声调，WITH_TONE_MARK--带声调
		PinyinFormat def=PinyinFormat.WITH_TONE_MARK;
		if(is.equals("num"))
			def=PinyinFormat.WITH_TONE_NUMBER;
		if(is.equals("no"))
			def=PinyinFormat.WITHOUT_TONE;
		return convertToPinyinString(str, separator, def);
	}

	/**
	 * 判断一个汉字是否为多音字
	 * 
	 * @param c
	 *            汉字
	 * @return 判断结果，是汉字返回true，否则返回false
	 */
	public static boolean hasMultiPinyin(char c) {
		String[] pinyinArray = convertToPinyinArray(c);
		if (pinyinArray != null && pinyinArray.length > 1) {
			return true;
		}
		return false;
	}

	/**
	 * 获取字符串对应拼音的首字母
	 * 
	 * @param str
	 *            需要转换的字符串
	 * @return 对应拼音的首字母
	 * @throws PinyinException
	 */
	public static String getShortPinyin(String str) throws RuntimeException {
		String separator = "#"; // 使用#作为拼音分隔符
		StringBuilder sb = new StringBuilder();

		char[] charArray = new char[str.length()];
		for (int i = 0, len = str.length(); i < len; i++) {
			char c = str.charAt(i);

			// 首先判断是否为汉字或者〇，不是的话直接将该字符返回
			if (!Chinese.isChinese(c) && c != CHINESE_LING) {
				charArray[i] = c;
			} else {
				int j = i + 1;
				sb.append(c);

				// 搜索连续的汉字字符串
				while (j < len && (Chinese.isChinese(str.charAt(j)) || str.charAt(j) == CHINESE_LING)) {
					sb.append(str.charAt(j));
					j++;
				}
				String hanziPinyin = convertToPinyinString(sb.toString(), separator, PinyinFormat.WITHOUT_TONE);
				String[] pinyinArray = hanziPinyin.split(separator);
				for (String string : pinyinArray) {
					charArray[i] = string.charAt(0);
					i++;
				}
				i--;
				sb.setLength(0);
			}
		}
		return String.valueOf(charArray);
	}

	public static void addPinyinDict(String path) throws FileNotFoundException {
		PINYIN_TABLE.putAll(Streams.resource(Streams.fileReader(path)));
	}

	public static void addMutilPinyinDict(String path) throws FileNotFoundException {
		MUTIL_PINYIN_TABLE.putAll(Streams.resource(Streams.fileReader(path)));
		dict.clear();
		DOUBLE_ARRAY_TRIE.clear();
		for (String word : MUTIL_PINYIN_TABLE.keySet()) {
			dict.add(word);
		}
		Collections.sort(dict);
		DOUBLE_ARRAY_TRIE.build(dict);
	}
	
	/**
     * 将汉字转换为全拼
     */
    public static String getPingYin(String name) {
        char[] charArray = name.toCharArray();
        StringBuilder pinyin = new StringBuilder();
        for (int i = 0; i < charArray.length; i++) {
            if (Character.toString(charArray[i]).matches("[\\u4E00-\\u9FA5]+")) {
                pinyin.append(convertToPinyinArray(charArray[i])[0]);
            } else {
                pinyin.append(charArray[i]);
            }
        }
        return pinyin.toString();
    }

    /**
     * 返回中文的首字母
     *
     * @param str
     * @return
     */
    public static String getPinYinHeadChar(String str) {

        String convert = "";
        for (int j = 0; j < str.length(); j++) {
            char word = str.charAt(j);
            String[] pinyinArray = convertToPinyinArray(word);
            if (pinyinArray != null) {
                convert += pinyinArray[0].charAt(0);
            } else {
                convert += word;
            }
        }
        return convert;
    }

    /**
     * 将字符串转移为ASCII码
     *
     * @param cnStr
     * @return
     */
    public static String getCnASCII(String cnStr) {
        StringBuffer strBuf = new StringBuffer();
        byte[] bGBK = cnStr.getBytes();
        for (int i = 0; i < bGBK.length; i++) {
            strBuf.append(Integer.toHexString(bGBK[i] & 0xff));
        }
        return strBuf.toString();
    }
    
    static class Node {
        int code;
        int depth;
        int left;
        int right;
    };
    
    static class DoubleArrayTrie {
        private final static int BUF_SIZE = 16384;
        private final static int UNIT_SIZE = 8; // size of int + int

        

        private int check[];
        private int base[];

        private boolean used[];
        private int size;
        private int allocSize;
        private List<String> key;
        private int keySize;
        private int length[];
        private int value[];
        private int progress;
        private int nextCheckPos;
        // boolean no_delete_;
        int error_;

        // int (*progressfunc_) (size_t, size_t);

        // inline _resize expanded
        private int resize(int newSize) {
            int[] base2 = new int[newSize];
            int[] check2 = new int[newSize];
            boolean used2[] = new boolean[newSize];
            if (allocSize > 0) {
                System.arraycopy(base, 0, base2, 0, allocSize);
                System.arraycopy(check, 0, check2, 0, allocSize);
                System.arraycopy(used2, 0, used2, 0, allocSize);
            }

            base = base2;
            check = check2;
            used = used2;

            return allocSize = newSize;
        }

        private int fetch(Node parent, List<Node> siblings) {
            if (error_ < 0)
                return 0;

            int prev = 0;

            for (int i = parent.left; i < parent.right; i++) {
                if ((length != null ? length[i] : key.get(i).length()) < parent.depth)
                    continue;

                String tmp = key.get(i);

                int cur = 0;
                if ((length != null ? length[i] : tmp.length()) != parent.depth)
                    cur = (int) tmp.charAt(parent.depth) + 1;

                if (prev > cur) {
                    error_ = -3;
                    return 0;
                }

                if (cur != prev || siblings.size() == 0) {
                    Node tmp_node = new Node();
                    tmp_node.depth = parent.depth + 1;
                    tmp_node.code = cur;
                    tmp_node.left = i;
                    if (siblings.size() != 0)
                        siblings.get(siblings.size() - 1).right = i;

                    siblings.add(tmp_node);
                }

                prev = cur;
            }

            if (siblings.size() != 0)
                siblings.get(siblings.size() - 1).right = parent.right;

            return siblings.size();
        }

        private int insert(List<Node> siblings) {
            if (error_ < 0)
                return 0;

            int begin = 0;
            int pos = ((siblings.get(0).code + 1 > nextCheckPos) ? siblings.get(0).code + 1 : nextCheckPos) - 1;
            int nonzero_num = 0;
            int first = 0;

            if (allocSize <= pos)
                resize(pos + 1);

            outer: while (true) {
                pos++;

                if (allocSize <= pos)
                    resize(pos + 1);

                if (check[pos] != 0) {
                    nonzero_num++;
                    continue;
                } else if (first == 0) {
                    nextCheckPos = pos;
                    first = 1;
                }

                begin = pos - siblings.get(0).code;
                if (allocSize <= (begin + siblings.get(siblings.size() - 1).code)) {
                    // progress can be zero
                    double l = (1.05 > 1.0 * keySize / (progress + 1)) ? 1.05 : 1.0 * keySize / (progress + 1);
                    resize((int) (allocSize * l));
                }

                if (used[begin])
                    continue;

                for (int i = 1; i < siblings.size(); i++)
                    if (check[begin + siblings.get(i).code] != 0)
                        continue outer;

                break;
            }

            // -- Simple heuristics --
            // if the percentage of non-empty contents in check between the
            // index
            // 'next_check_pos' and 'check' is greater than some constant value
            // (e.g. 0.9),
            // new 'next_check_pos' index is written by 'check'.
            if (1.0 * nonzero_num / (pos - nextCheckPos + 1) >= 0.95)
                nextCheckPos = pos;

            used[begin] = true;
            size = (size > begin + siblings.get(siblings.size() - 1).code + 1) ? size : begin + siblings.get(siblings.size() - 1).code + 1;

            for (int i = 0; i < siblings.size(); i++)
                check[begin + siblings.get(i).code] = begin;

            for (int i = 0; i < siblings.size(); i++) {
                List<Node> new_siblings = new ArrayList<Node>();

                if (fetch(siblings.get(i), new_siblings) == 0) {
                    base[begin + siblings.get(i).code] = (value != null) ? (-value[siblings.get(i).left] - 1) : (-siblings.get(i).left - 1);

                    if (value != null && (-value[siblings.get(i).left] - 1) >= 0) {
                        error_ = -2;
                        return 0;
                    }

                    progress++;
                    // if (progress_func_) (*progress_func_) (progress,
                    // keySize);
                } else {
                    int h = insert(new_siblings);
                    base[begin + siblings.get(i).code] = h;
                }
            }
            return begin;
        }

        public DoubleArrayTrie() {
            check = null;
            base = null;
            used = null;
            size = 0;
            allocSize = 0;
            // no_delete_ = false;
            error_ = 0;
        }

        // no deconstructor

        // set_result omitted
        // the search methods returns (the list of) the value(s) instead
        // of (the list of) the pair(s) of value(s) and length(s)

        // set_array omitted
        // array omitted

        void clear() {
            // if (! no_delete_)
            check = null;
            base = null;
            used = null;
            allocSize = 0;
            size = 0;
            // no_delete_ = false;
        }

        public int getUnitSize() {
            return UNIT_SIZE;
        }

        public int getSize() {
            return size;
        }

        public int getTotalSize() {
            return size * UNIT_SIZE;
        }

        public int getNonzeroSize() {
            int result = 0;
            for (int i = 0; i < size; i++)
                if (check[i] != 0)
                    result++;
            return result;
        }

        public int build(List<String> key) {
            return build(key, null, null, key.size());
        }

        public int build(List<String> _key, int _length[], int _value[], int _keySize) {
            if (_keySize > _key.size() || _key == null)
                return 0;

            // progress_func_ = progress_func;
            key = _key;
            length = _length;
            keySize = _keySize;
            value = _value;
            progress = 0;

            resize(65536 * 32);

            base[0] = 1;
            nextCheckPos = 0;

            Node root_node = new Node();
            root_node.left = 0;
            root_node.right = keySize;
            root_node.depth = 0;

            List<Node> siblings = new ArrayList<Node>();
            fetch(root_node, siblings);
            insert(siblings);

            // size += (1 << 8 * 2) + 1; // ???
            // if (size >= allocSize) resize (size);

            used = null;
            key = null;

            return error_;
        }

        public void open(String fileName) throws IOException {
            File file = new File(fileName);
            size = (int) file.length() / UNIT_SIZE;
            check = new int[size];
            base = new int[size];

            DataInputStream is = null;
            try {
                is = new DataInputStream(new BufferedInputStream(new FileInputStream(file), BUF_SIZE));
                for (int i = 0; i < size; i++) {
                    base[i] = is.readInt();
                    check[i] = is.readInt();
                }
            } finally {
                if (is != null)
                    is.close();
            }
        }

        public void save(String fileName) throws IOException {
            DataOutputStream out = null;
            try {
                out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(fileName)));
                for (int i = 0; i < size; i++) {
                    out.writeInt(base[i]);
                    out.writeInt(check[i]);
                }
                out.close();
            } finally {
                if (out != null)
                    out.close();
            }
        }

        public int exactMatchSearch(String key) {
            return exactMatchSearch(key, 0, 0, 0);
        }

        public int exactMatchSearch(String key, int pos, int len, int nodePos) {
            if (len <= 0)
                len = key.length();
            if (nodePos <= 0)
                nodePos = 0;

            int result = -1;

            char[] keyChars = key.toCharArray();

            int b = base[nodePos];
            int p;

            for (int i = pos; i < len; i++) {
                p = b + (int) (keyChars[i]) + 1;
                if (b == check[p])
                    b = base[p];
                else
                    return result;
            }

            p = b;
            int n = base[p];
            if (b == check[p] && n < 0) {
                result = -n - 1;
            }
            return result;
        }

        public List<Integer> commonPrefixSearch(String key) {
            return commonPrefixSearch(key, 0, 0, 0);
        }

        public List<Integer> commonPrefixSearch(String key, int pos, int len, int nodePos) {
            if (len <= 0)
                len = key.length();
            if (nodePos <= 0)
                nodePos = 0;

            List<Integer> result = new ArrayList<Integer>();

            char[] keyChars = key.toCharArray();

            int b = base[nodePos];
            int n;
            int p;

            for (int i = pos; i < len; i++) {
                p = b;
                n = base[p];

                if (b == check[p] && n < 0) {
                    result.add(-n - 1);
                }

                p = b + (int) (keyChars[i]) + 1;
                if (b == check[p])
                    b = base[p];
                else
                    return result;
            }

            p = b;
            n = base[p];

            if (b == check[p] && n < 0) {
                result.add(-n - 1);
            }

            return result;
        }

        // debug
        public void dump() {
            for (int i = 0; i < size; i++) {
                System.err.println("i: " + i + " [" + base[i] + ", " + check[i] + "]");
            }
        }
    }

    
    public static void main(String[] args) {
		System.err.println(to("手机",""));
	}
}
